/*
 * CornerLayout.java
 *
 * Created on May 9, 2006 at 1:54:30 PM
 *
 * Copyright 2008 the Desert Labs Project (http://desertlabs.us)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package us.desertlabs.ui;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;

/**
 * This <CODE>LayoutManager</CODE> arranges <CODE>Component</CODE>s based on the notion of a "dominant
 * corner".  The arranged area is divided into four corner quadrants.  The preffered size of the <CODE>Component</CODE>
 * in the corner marked as "dominant" is used to determine the sizes of all of the other corners.
 *
 * @author Brandon Franklin
 */
public class CornerLayout implements LayoutManager2 {

    /** The enumeration representing the various corners */
    public enum Corner {

        NE, NW, SE, SW
    }

    /** An <CODE>Dimension</CODE> with width and height of zero */
    static final private Dimension ZERO_DIMENSION = new Dimension( 0, 0 );

    /** The "dominant corner" of this layout */
    private Corner dominantCorner;

    /** The horizontal gap between components */
    private int hgap = 0;

    /** The <CODE>Component</CODE> in the northeast corner */
    private Component neComp = null;

    /** The <CODE>Component</CODE> in the northwest corner */
    private Component nwComp = null;

    /** The <CODE>Component</CODE> in the southeast corner */
    private Component seComp = null;

    /** The <CODE>Component</CODE> in the southwest corner */
    private Component swComp = null;

    /** The vertical gap between components */
    private int vgap = 0;

    /**
     * Creates a new instance of <CODE>CornerLayout</CODE> with the specified
     * corner as the "dominant corner", and with no gaps between the components.
     *
     * @param corner the "dominant corner" of the new layout
     */
    public CornerLayout( final Corner corner ) {
        this( corner, 0, 0 );
    }

    /**
     * Creates a new instance of <CODE>CornerLayout</CODE> with the specified
     * corner as the "dominant corner", and with the specified gaps between the
     * components.
     *
     * @param corner the "dominant corner" of the new layout
     * @param hgap the horizontal gap between components
     * @param vgap the vertical gap between components
     */
    public CornerLayout( final Corner corner, final int hgap, final int vgap ) {
        if( corner == null ) {
            throw new IllegalArgumentException( "Dominant corner must be non-null" );
        }
        dominantCorner = corner;

        if( hgap < 0 ) {
            throw new IllegalArgumentException( "Horizontal gap must be must be non-negative" );
        }
        this.hgap = hgap;

        if( vgap < 0 ) {
            throw new IllegalArgumentException( "Vertical gap must be must be non-negative" );
        }
        this.vgap = vgap;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addLayoutComponent( final Component comp, final Object constraints ) {
        if( constraints instanceof Corner ) {

            synchronized( comp.getTreeLock() ) {
                final Corner cornerConstraint = (Corner)constraints;
                switch( cornerConstraint ) {
                    case NW:
                        nwComp = comp;
                        break;
                    case NE:
                        neComp = comp;
                        break;
                    case SE:
                        seComp = comp;
                        break;
                    case SW:
                    default:
                        swComp = comp;
                        break;
                }
            }
        } else {
            throw new IllegalArgumentException( "Constraints must be a Corner: " + constraints );
        }
    }

    /**
     * @deprecated replaced by
     *             <code>addLayoutComponent(Component, Object)</code>
     */
    @Deprecated
    @Override
    public void addLayoutComponent( final String name, final Component comp ) {
        if( "Northwest".equals( name ) ) {
            addLayoutComponent( comp, Corner.NW );
        } else if( "Northeast".equals( name ) ) {
            addLayoutComponent( comp, Corner.NE );
        } else if( "Southwest".equals( name ) ) {
            addLayoutComponent( comp, Corner.SW );
        } else if( "Southeast".equals( name ) ) {
            addLayoutComponent( comp, Corner.SE );
        } else {
            throw new IllegalArgumentException( "Unknown constraint: " + name );
        }
    }

    /**
     * Returns the <CODE>Component</CODE> that is currently in the dominant
     * corner of this layout.
     *
     * @return the <CODE>Component</CODE> that is currently in the dominant
     *         corner of this layout
     */
    public Component getDominantComponent() {

        switch( dominantCorner ) {
            case NW:
                return nwComp;
            case NE:
                return neComp;
            case SW:
                return swComp;

            case SE:
            default:
                return seComp;
        }
    }

    /**
     * Gets the dominant corner of this layout.
     *
     * @return the dominant corner of this layout
     */
    public Corner getDominantCorner() {
        return dominantCorner;
    }

    /**
     * Returns the horizontal gap between components.
     *
     * @return the horizontal gap between components
     */
    public int getHgap() {
        return hgap;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public float getLayoutAlignmentX( final Container target ) {
        return 0.5f;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public float getLayoutAlignmentY( final Container target ) {
        return 0.5f;
    }

    /**
     * Returns the vertical gap between components.
     *
     * @return the vertical gap between components
     */
    public int getVgap() {
        return vgap;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void invalidateLayout( final Container target ) {
        // Does nothing in this implementation
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void layoutContainer( final Container parent ) {
        synchronized( parent.getTreeLock() ) {
            final Insets insets = parent.getInsets();

            final int vLine = getVLine( parent, insets.left, insets.right );
            final int hLine = getHLine( parent, insets.top, insets.bottom );

            // NW component
            if( nwComp != null ) {
                final int x = insets.left;
                final int y = insets.top;
                int width = vLine - x;
                int height = hLine - y;

                // Adjust for horizontal gap
                if( (neComp != null) || (seComp != null) ) {
                    width -= hgap / 2;
                }

                // Adjust for vertical gap
                if( (swComp != null) || (seComp != null) ) {
                    height -= vgap / 2;
                }

                nwComp.setBounds( x, y, width, height );
            }

            // NE component
            if( neComp != null ) {
                int x = vLine;
                final int y = insets.top;
                int width = parent.getWidth() - vLine - insets.right;
                int height = hLine - y;

                // Adjust for horizontal gap
                if( (nwComp != null) || (swComp != null) ) {
                    x += hgap / 2;
                    width -= hgap / 2;
                }

                // Adjust for vertical gap
                if( (swComp != null) || (seComp != null) ) {
                    height -= vgap / 2;
                }

                neComp.setBounds( x, y, width, height );
            }

            // SW component
            if( swComp != null ) {
                final int x = insets.left;
                int y = hLine;
                int width = vLine - x;
                int height = parent.getHeight() - hLine - insets.bottom;

                // Adjust for horizontal gap
                if( (neComp != null) || (seComp != null) ) {
                    width -= hgap / 2;
                }

                // Adjust for vertical gap
                if( (nwComp != null) || (neComp != null) ) {
                    y += vgap / 2;
                    height -= vgap / 2;
                }

                swComp.setBounds( x, y, width, height );
            }

            // SE component
            if( seComp != null ) {
                int x = vLine;
                int y = hLine;
                int width = parent.getWidth() - vLine - insets.right;
                int height = parent.getHeight() - hLine - insets.bottom;

                // Adjust for horizontal gap
                if( (nwComp != null) || (swComp != null) ) {
                    x += hgap / 2;
                    width -= hgap / 2;
                }

                // Adjust for vertical gap
                if( (nwComp != null) || (neComp != null) ) {
                    y += vgap / 2;
                    height -= vgap / 2;
                }

                seComp.setBounds( x, y, width, height );
            }

        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Dimension maximumLayoutSize( final Container target ) {
        return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Dimension minimumLayoutSize( final Container parent ) {
        synchronized( parent.getTreeLock() ) {
            final Dimension dim = ZERO_DIMENSION;

            final Dimension nwMin = (nwComp != null ? nwComp.getMinimumSize() : ZERO_DIMENSION);
            final Dimension neMin = (neComp != null ? neComp.getMinimumSize() : ZERO_DIMENSION);
            final Dimension swMin = (swComp != null ? swComp.getMinimumSize() : ZERO_DIMENSION);
            final Dimension seMin = (seComp != null ? seComp.getMinimumSize() : ZERO_DIMENSION);

            dim.width += Math.max( nwMin.width, swMin.width );
            dim.width += Math.max( neMin.width, seMin.width );

            dim.height += Math.max( nwMin.height, neMin.height );
            dim.height += Math.max( swMin.height, seMin.height );

            if( ((nwComp != null) || (swComp != null)) && ((neComp != null) || (seComp != null)) ) {
                dim.width += hgap;
            }

            if( ((nwComp != null) || (neComp != null)) && ((swComp != null) || (seComp != null)) ) {
                dim.height += vgap;
            }

            final Insets insets = parent.getInsets();
            dim.width += insets.left + insets.right;
            dim.height += insets.top + insets.bottom;

            return dim;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Dimension preferredLayoutSize( final Container parent ) {
        synchronized( parent.getTreeLock() ) {
            final Dimension dim = ZERO_DIMENSION;

            final Dimension nwPref = (nwComp != null ? nwComp.getPreferredSize() : ZERO_DIMENSION);
            final Dimension nePref = (neComp != null ? neComp.getPreferredSize() : ZERO_DIMENSION);
            final Dimension swPref = (swComp != null ? swComp.getPreferredSize() : ZERO_DIMENSION);
            final Dimension sePref = (seComp != null ? seComp.getPreferredSize() : ZERO_DIMENSION);

            dim.width += Math.max( nwPref.width, swPref.width );
            dim.width += Math.max( nePref.width, sePref.width );

            dim.height += Math.max( nwPref.height, nePref.height );
            dim.height += Math.max( swPref.height, sePref.height );

            if( ((nwComp != null) || (swComp != null)) && ((neComp != null) || (seComp != null)) ) {
                dim.width += hgap;
            }

            if( ((nwComp != null) || (neComp != null)) && ((swComp != null) || (seComp != null)) ) {
                dim.height += vgap;
            }

            final Insets insets = parent.getInsets();
            dim.width += insets.left + insets.right;
            dim.height += insets.top + insets.bottom;

            return dim;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeLayoutComponent( final Component comp ) {
        synchronized( comp.getTreeLock() ) {
            if( comp == nwComp ) {
                nwComp = null;
            } else if( comp == neComp ) {
                neComp = null;
            } else if( comp == swComp ) {
                swComp = null;
            } else if( comp == seComp ) {
                seComp = null;
            }

        }
    }

    /**
     * Sets the dominant corner of this layout.
     *
     * @param corner the dominant corner of this layout
     */
    public void setDominantCorner( final Corner corner ) {
        if( corner == null ) {
            throw new IllegalArgumentException( "Dominant corner must be non-null" );
        }
        dominantCorner = corner;
    }

    /**
     * Sets the horizontal gap between components.
     *
     * @param hgap the horizontal gap between components
     */
    public void setHgap( final int hgap ) {
        if( hgap < 0 ) {
            throw new IllegalArgumentException( "Horizontal gap must be must be non-negative" );
        }
        this.hgap = hgap;
    }

    /**
     * Sets the vertical gap between components.
     *
     * @param vgap the vertical gap between components
     */
    public void setVgap( final int vgap ) {
        if( vgap < 0 ) {
            throw new IllegalArgumentException( "Vertical gap must be must be non-negative" );
        }
        this.vgap = vgap;
    }

    /**
     * Returns a <CODE>String</CODE> representation of this layout.
     *
     * @return a <CODE>String</CODE> representation of this layout.
     */
    @Override
    public String toString() {
        return getClass().getName() + "[dominantCorner=" + dominantCorner + ",hgap=" + hgap + ",vgap=" + vgap + "]";
    }

    /**
     * Returns the position of the horizontal line used to divide components.
     *
     * @param parent the parent <CODE>Container</CODE>
     * @param top the top-side inset
     * @param bottom the bottom-side inset
     * @return the position of the horizontal line used to divide components
     */
    private int getHLine( final Container parent, final int top, final int bottom ) {
        final Component comp = getDominantComponent();
        if( comp == null ) {
            return ((parent.getHeight() - top - bottom) / 2) + top;
        }

        int linePos = 0;
        switch( dominantCorner ) {
            case NW:
            case NE:
                linePos = comp.getPreferredSize().height;
                linePos += top;
                break;
            case SW:
            case SE:
            default:
                linePos = parent.getHeight() - comp.getPreferredSize().height;
                linePos -= bottom;
                break;
        }

        return linePos;
    }

    /**
     * Returns the position of the vertical line used to divide components.
     *
     * @param parent the parent <CODE>Container</CODE>
     * @param left the left-side inset
     * @param right the right-side inset
     * @return the position of the vertical line used to divide components
     */
    private int getVLine( final Container parent, final int left, final int right ) {
        final Component comp = getDominantComponent();
        if( comp == null ) {
            return ((parent.getWidth() - left - right) / 2) + left;
        }

        int linePos = 0;
        switch( dominantCorner ) {
            case NW:
            case SW:
                linePos = comp.getPreferredSize().width;
                linePos += left;
                break;
            case NE:
            case SE:
            default:
                linePos = parent.getWidth() - comp.getPreferredSize().width;
                linePos -= right;
                break;
        }

        return linePos;
    }
}
